﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Threading;
using SpectrumAnalyser.Common.Helpers;
using SpectrumAnalyser.Controls.Wpf.ViewModels;
using SpectrumAnalyser.CustomControls.Wpf;
using SpectrumAnalyser.ModelObjects;
using SpectrumAnalyser.Shell.ViewModels;

namespace SpectrumAnalyser.Shell.Controls
{
    public class CollectionFilter<T1, T2> : FrameworkElement, INotifyPropertyChanged
    {
        public CollectionFilter()
        {
            FilteredResult = new ObservableCollection<T2>();
            Source = new ObservableCollection<T1>();
        }

        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof (ObservableCollection<T1>), typeof (CollectionFilter<T1, T2>),
                                        new PropertyMetadata(default(ObservableCollection<T1>)));

        public ObservableCollection<T1> Source
        {
            get
            {
                return (ObservableCollection<T1>) GetValue(SourceProperty);
            }
            set
            {
                SetValue(SourceProperty, value);
                foreach (var t1 in value)
                {
                    if (t1 is INotifyPropertyChanged)
                    {
                        (t1 as INotifyPropertyChanged).PropertyChanged += (sender, args) => FilterInner();
                    }
                }

                FilterInner();
                OnPropertyChanged("Source");
            }
        }

        private void FilterInner()
        {
            FilteredResult.Clear();
            foreach (var t1 in Source)
            {
                IList<T2> t2Res;
                Filter(t1, out t2Res);
                if (!ReferenceEquals(t2Res, null))
                {
                    foreach (var t2Re in t2Res)
                    {
                        FilteredResult.Add(t2Re);
                    }
                }
            }
            FilteredResultBinding = FilteredResult;
        }

        public static readonly DependencyProperty FilteredResultProperty =
            DependencyProperty.Register("FilteredResult", typeof (ObservableCollection<T2>),
                                        typeof (CollectionFilter<T1, T2>),
                                        new PropertyMetadata(default(ObservableCollection<T2>)));

        public ObservableCollection<T2> FilteredResult
        {
            get
            {
                return (ObservableCollection<T2>) GetValue(FilteredResultProperty);
            }
            set
            {
                SetValue(FilteredResultProperty, value);
                OnPropertyChanged("FilteredResult");
            }
        }

        public static readonly DependencyProperty FilteredResultBindingProperty =
            DependencyProperty.Register("FilteredResultBinding", typeof (ObservableCollection<T2>), typeof (CollectionFilter<T1,T2>), new PropertyMetadata(default(ObservableCollection<T2>)));

        public ObservableCollection<T2> FilteredResultBinding
        {
            get { return (ObservableCollection<T2>) GetValue(FilteredResultBindingProperty); }
            set
            {
                SetValue(FilteredResultBindingProperty, value);
                foreach (var v in value)
                {
                    if(v is INotifyPropertyChanged)
                    {
                        (v as INotifyPropertyChanged).PropertyChanged +=
                            (o, e) => OnPropertyChanged("FilteredResultBinding");
                    }
                }
                OnPropertyChanged("FilteredResultBinding");
            }
        }

        public delegate void FilterEventHandler(T1 input, out IList<T2> output);

        public event FilterEventHandler Filter;

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (!ReferenceEquals(PropertyChanged, null))
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }

    /// <summary>
    /// Interaction logic for GraphicRenderControl.xaml
    /// </summary>
    public partial class GraphicRenderControl : UserControl, INotifyPropertyChanged
    {
        public ObservableCollection<ChemicalElement> SelectedChemicalElements = new ObservableCollection<ChemicalElement>();

        public CollectionViewSource Source = new CollectionViewSource();

        private HorizontalLineChart LineChart;
        public GraphicRenderControl()
        {
            InitializeComponent();
            LineChart = new HorizontalLineChart();
            ChartContent.Children.Add(LineChart);
            this.DataContext = this;
        }

        public static readonly DependencyProperty ProcessedSpectrumFileProperty =
            DependencyProperty.Register("ProcessedSpectrumFile", typeof(SpectrumFileViewModel), typeof(GraphicRenderControl),
                                        new PropertyMetadata(default(SpectrumFileViewModel)));

        public SpectrumFileViewModel ProcessedSpectrumFile
        {
            get { return (SpectrumFileViewModel)GetValue(ProcessedSpectrumFileProperty); }
            set { SetValue(ProcessedSpectrumFileProperty, value); }
        }

        public ObservableCollection<Peak> Peaks
        {
            get { return LineChart.Peaks; }
            set
            {
                if (LineChart.Peaks != null)
                {
                    foreach (var peak in LineChart.Peaks)
                    {
                        peak.PropertyChanged -= OnPeaksPropertyChanged;
                        if (peak.MatchedChemicalElements == null)
                        {
                            continue;
                        }
                        foreach (var matchedChemicalElement in peak.MatchedChemicalElements)
                        {
                            matchedChemicalElement.PropertyChanged -= OnChemicalElementPropertyChanged;
                        }
                    }
                }
                LineChart.Peaks = new ObservableCollection<Peak>(value);
                foreach (var peak in LineChart.Peaks)
                {
                    peak.PropertyChanged += OnPeaksPropertyChanged;
                    if (peak.MatchedChemicalElements == null)
                    {
                        continue;
                    }
                    foreach (var matchedChemicalElement in peak.MatchedChemicalElements)
                    {
                        matchedChemicalElement.PropertyChanged += OnChemicalElementPropertyChanged;
                    }
                }
                LineChart.Render();

                OnPropertyChanged(StaticReflection.GetMemberName<GraphicRenderControl>(x => x.Peaks));

                //force to update headers.
                UpdateChemicalElementsInfo();
                UpdateSelectedChemicalElementsInfo();
            }
        }

        private void OnChemicalElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == StaticReflection.GetMemberName<ChemicalElement>(x => x.IsChecked))
            {
                UpdateSelectedChemicalElementsInfo();
            }
            if (e.PropertyName == StaticReflection.GetMemberName<ChemicalElement>(x => x.IsVisible))
            {
                LineChart.Render();
            }
        }

        private void UpdateSelectedChemicalElementsInfo()
        {
            SelectedChemicalElementsInfo.ItemsSource =
                    ChemicalElementsInfo.ItemsSource.Cast<ChemicalElement>().Where(x => x.IsChecked).ToList();
            LineChart.Render();
        }

        private void OnPeaksPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == StaticReflection.GetMemberName<Peak>(x => x.IsVisible))
            {
                UpdateChemicalElementsInfo();
            }
            //ChemicalElementsInfoTab.GetBindingExpression(TabItem.HeaderProperty).UpdateTarget();
        }

        private void UpdateChemicalElementsInfo()
        {
            ChemicalElementsInfo.ItemsSource =
                    Peaks.Where(x => x.IsVisible && x.MatchedChemicalElements != null).SelectMany(
                        x => x.MatchedChemicalElements).ToList();
            LineChart.Render();
        }

        public void ExportImage(Stream stream)
        {
            //LineChart.Export(stream);
        }

        private void RenderFile(SpectrumFileViewModel file)
        {
            if (ReferenceEquals(file, null))
            {
                return;
            }
            var data =
                new ObservableCollection<SpectrumDataPointViewModel>(file.DataPoints.Cast<SpectrumDataPointViewModel>());
            LineChart.Source = data;
            LineChart.WaveletSource = file.Wavelet;
            Peaks = file.Peaks;
            LineChart.Render();
        }

        public void RenderFiles(IList<SpectrumFileViewModel> files)
        {
            if (files.Any(x => x.DataPoints == null))
            {
                return;
            }

            var waveletSizeCoefficient = (int)App.AppConfigHelper.GetPeaksSearchSettings().WaveletCoefficient;

            foreach (var spectrumFile in files)
            {
                RenderFile(spectrumFile);

                ProcessedSpectrumFile = spectrumFile;

                //ProgressControl.ReportProgress(100*(files.IndexOf(spectrumFile) + 1)/files.Count);
            }
            Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
                                                                             {
                                                                                 
                                                                             }));
        }

        #region Minimap

        /*private void UpdateMinimapSource()
        {
            var height = (int) ChartCanvas.ActualHeight;
            var width = (int) ChartCanvas.ActualWidth;
            if (height == 0 || width == 0)
                return;

            var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
            bmp.Render(ChartCanvas);
            MinimapPresenter.Content = null;
            MinimapPresenter.Content = new Image {Source = bmp};

            UpdateMinimapSize();
        }

        private void UpdateMinimapSize()
        {
            MinimapCanvas.Height = ChartCanvas.ActualHeight/(ChartCanvas.ActualWidth/150.0);
            MinimapPresenter.Height = ChartCanvas.ActualHeight/(ChartCanvas.ActualWidth/150.0);
            MinimapVisibilityAreaBorder.Width = MinimapCanvas.Width;
            MinimapVisibilityAreaBorder.Height = MinimapCanvas.Height;
            //MinimapVisibilityAreaBorder.
            _previousMousePosition = new SpectrumPoint(MinimapCanvas.Width/2, MinimapCanvas.Height/2);
            MinimapCanvas.UpdateLayout();
        }

        private void MinimapCanvasMouseDown(bool useOld)
        {
            if (Math.Abs(MinimapCanvas.ActualHeight - 0) <= 0)
                return;

            _previousMousePosition = useOld ? _previousMousePosition : Mouse.GetPosition(MinimapCanvas);
            var mmbWidth = MinimapVisibilityAreaBorder.ActualWidth/ScaleSlider.Value;
            var mmbHeight = MinimapVisibilityAreaBorder.ActualHeight/ScaleSlider.Value;
            if (Math.Abs(mmbWidth - 0.0) <= 0 && Math.Abs(mmbHeight - 0.0) <= 0)
                return;

            var mmcWidth = ChartCanvas.ActualWidth*ScaleSlider.Value;
            var mmcHeight = ChartCanvas.ActualHeight*ScaleSlider.Value;

            var positionLeftX = Math.Max(_previousMousePosition.X - mmbWidth/2, 0);
            positionLeftX = Math.Min(MinimapCanvas.ActualWidth - mmbWidth, positionLeftX);
            var positionTopY = Math.Max(_previousMousePosition.Y - mmbHeight/2, 0);
            positionTopY = Math.Min(MinimapCanvas.ActualHeight - mmbHeight, positionTopY);

            Canvas.SetLeft(MinimapVisibilityAreaBorder, positionLeftX);
            Canvas.SetTop(MinimapVisibilityAreaBorder, positionTopY);

            /*HorizontalChart.ScrollTo(mmcWidth*positionLeftX/MinimapCanvas.ActualWidth,
                                     mmcHeight*positionTopY/MinimapCanvas.ActualHeight);#1#
        }

        private void ScaleSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            MinimapCanvasMouseDown(true);
            //HorizontalChart.Scale = e.NewValue;
        }

        private void ZoomValuePreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            double result;
            if (!double.TryParse(e.Text, out result))
            {
                e.Handled = true;
            }
            if (result > ScaleSlider.Maximum)
            {
                var textBox = sender as TextBox;
                if (textBox != null)
                {
                    textBox.Text = 10.ToString(CultureInfo.InvariantCulture);
                }
            }
        }*/

        
        #endregion

        /*private void ZoomInClick(object sender, RoutedEventArgs e)
        {
            lineChart1.Behavior = Behavior.ZoomChart;
            lineChart1.PreviewMouseDown += (o, args) =>
                {
                    (lineChart1.Series[0] as LineSeries).Labels.Clear();
                };
        }

        private void ZoomOutClick(object sender, RoutedEventArgs e)
        {
            lineChart1.ZoomOut();
        }

        private void ZoomResetClick(object sender, RoutedEventArgs e)
        {
            lineChart1.ResetZoom();
            (lineChart1.Series[0] as LineSeries).Labels = _file.Data.Select(x => x.LabelText).ToList();
            lineChart1.UpdateChart();
        }*/

        /*private void FilterElements(object sender, RoutedEventArgs routedEventArgs)
        {
            if (sender is TextBox)
            {
                ChemicalElementsInfo.Items.Filter -= FilterElements;
                var filterText = (sender as TextBox).Text;

                ChemicalElementsInfo.Items.Filter += FilterElements;
            }
        }

        private bool FilterElements(object o)
        {
            var emp = (ChemicalElement) o;
            string str = emp.Name.ToLower();
            if (String.IsNullOrEmpty(str)) return false;
            int index = str.IndexOf(ElementsFilterTextBox.Text.ToLower(), 0);
            return (index > -1);
        }*/

        private void RemoveSelectedPeak(object sender, RoutedEventArgs e)
        {
            var selectedPeak = PeaksInfo.SelectedItem as Peak;
            if (!ReferenceEquals(selectedPeak, null))
            {
                (PeaksInfo.ItemsSource as ObservableCollection<Peak>).Remove(selectedPeak);
            }
        }

        private void PeaksInfoDoubleClick(object sender, MouseButtonEventArgs e)
        {
            IInputElement element = e.MouseDevice.DirectlyOver;
            if (element != null && element is FrameworkElement)
            {
                if (((FrameworkElement) element).Parent is DataGridCell)
                {
                    var grid = sender as DataGrid;
                    if (grid != null && grid.SelectedItems != null && grid.SelectedItems.Count == 1)
                    {
                        var rowview = grid.SelectedItem as Peak;
                        if (rowview != null)
                        {
                            rowview.IsVisible = !rowview.IsVisible;
                        }
                    }
                }
            }
        }

        private void ChemicalElementsInfoDoubleClick(object sender, MouseButtonEventArgs e)
        {
            IInputElement element = e.MouseDevice.DirectlyOver;
            if (element != null && element is FrameworkElement)
            {
                if (((FrameworkElement)element).Parent is DataGridCell)
                {
                    var grid = sender as DataGrid;
                    if (grid != null && grid.SelectedItems != null && grid.SelectedItems.Count == 1)
                    {
                        var rowview = grid.SelectedItem as ChemicalElement;
                        if (rowview != null)
                        {
                            rowview.IsChecked = !rowview.IsChecked;
                        }
                    }
                }
            }
        }

        private void SelectedChemicalElementsInfoDoubleClick(object sender, MouseButtonEventArgs e)
        {
            IInputElement element = e.MouseDevice.DirectlyOver;
            if (element != null && element is FrameworkElement)
            {
                if (((FrameworkElement)element).Parent is DataGridCell)
                {
                    var grid = sender as DataGrid;
                    if (grid != null && grid.SelectedItems != null && grid.SelectedItems.Count == 1)
                    {
                        var rowview = grid.SelectedItem as ChemicalElement;
                        if (rowview != null)
                        {
                            rowview.IsVisible = !rowview.IsVisible;
                        }
                    }
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        public void ResetZoom()
        {
            LineChart.ResetZoom();
        }
    }
}
